﻿using AspNetCoreRateLimit;
using YNTK.Common.Cache;
using YNTK.Common.Core.Filter;
using YNTK.Data.SqlSugar.Extensions;
using YNTK.JsonSerialization;
using YNTK.Message.Extensions;
using YNTK.TaskScheduler.Interfaces.TaskScheduler;
using YNTK.UnifyResult;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OnceMi.AspNetCore.OSS;
using Senparc.CO2NET;
using Senparc.CO2NET.RegisterServices;
using Senparc.Weixin;
using Senparc.Weixin.Entities;
using Senparc.Weixin.RegisterServices;
using Serilog;
using SqlSugar;
using SqlSugar.IOC;
using System;
using System.Collections.Generic;
using Yitter.IdGenerator;
using YNTK.Data.MongoDB.Extensions;
using YNTK.Data.MongoDB;
using Microsoft.AspNetCore.Http.Features;
using YNTK.Data.MongoDB.Repositorie;

namespace YNTK.API.Core
{
    /// <summary>
    /// 注册服务启动配置
    /// </summary>
    [AppStartup(9)]
    public class Startup : AppStartup
    {
        /// <summary>
        /// 配置服务
        /// 此方法由运行时调用,使用此方法将服务添加到容器
        /// </summary>
        /// <param name="services">指定服务描述符集合的契约</param>
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<FormOptions>(options =>
            {
                options.ValueLengthLimit = int.MaxValue;
                options.MultipartBodyLengthLimit = int.MaxValue;
            });

            #region 配置跨域处理
            services.AddCors(op =>
            {
                //自定义的跨域策略
                op.AddPolicy("any", 
                    set => set.SetIsOriginAllowed(origin => true)//为基础策略设置指定的isOriginAllowed
                    .AllowAnyHeader()//确保策略允许任何标头
                    .AllowAnyMethod()//确保策略允许任何方法
                    .AllowCredentials()//设置策略以允许凭据
                    );
            });
            #endregion
           
            //配置SqlSugar
            SqlSugarConfigure(services);
            //配置MongoDB
            MongoDbConfigure(services);

            //添加 JWT 授权
            services.AddJwt<JwtHandler>(enableGlobalAuthorize: true);

            //配置跨域
            services.AddCorsAccessor();

            //注册远程请求
            services.AddRemoteRequest();

            //添加选项配置
            services.AddConfigurableOptions<CacheOptions>();

            //将控制器的服务添加到指定的Microsoft.Extensions.DependencyInjection.IServiceCollection
            //此方法不会注册用于页面的服务
            services.AddControllersWithViews()
                    .AddMvcFilter<RequestActionFilter>()//注册 Mvc 过滤器,请求日志拦截
                    .AddInjectWithUnifyResult<RESTfulResultProvider>()//Mvc 注入基础配置和规范化结果,RESTful 风格返回值
                     //.AddJsonOptions(options =>
                     //{
                     //    //options.JsonSerializerOptions.Converters.AddDateFormatString("yyyy-MM-dd HH:mm:ss");
                     //    //格式化日期时间格式
                     //    options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
                     //});

                     //通过Microsoft.Extensions.DependencyInjection.IMvcBuilder配置MVC的扩展方法
                     .AddNewtonsoftJson(options =>
                     {
                         //options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                         //默认命名规则
                         options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                         //设置时区为 UTC
                         options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
                         //格式化json输出的日期格式
                         options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
                         //格式化json输出的日期格式为时间戳
                         //options.SerializerSettings.Converters.Add(new NewtonsoftDateTimeJsonConverter());
                         //空值处理
                         //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                     });

            //添加视图引擎
            services.AddViewEngine();

            #region 对象储存
            /* OnceMi.AspNetCore.OSS 对象储存扩展包
             * 支持Minio自建对象储存、阿里云OSS、腾讯云COS、七牛云Kodo、华为云OBS
             */
            #region Minio自建对象储存
            //添加默认对象储存配置信息
            services.AddOSSService(option => 
            {
                option.Provider = OSSProvider.Minio;//提供者,枚举值支持：Minio/Aliyun/QCloud
                option.Endpoint = "192.168.0.60:9000";//端点,配置文件接口访问域名
                option.AccessKey = "yntkkjminioadmin";//访问密钥,用户名
                option.SecretKey = "yntkkjminioadmin";//秘密密钥,密码
                option.Region = "192.168.0.60";//地址
                option.IsEnableHttps = false;//是否启用Https,建议启用
                option.IsEnableCache = true;//是否启用缓存,启用后将缓存签名URL，以减少请求次数
                //option.SessionToken ="";//token,仅Minio中使用
            });
            #endregion

            #region 阿里云OSS
            //添加名称为‘aliyunoss’的OSS对象储存配置信息
            services.AddOSSService("aliyunoss", option =>
            {
                option.Provider = OSSProvider.Aliyun;//提供者,枚举值支持：Minio/Aliyun/QCloud
                option.Endpoint = "oss-cn-hangzhou.aliyuncs.com";//端点
                option.AccessKey = "aliyunoss_yntkkjminioadmin";//访问密钥
                option.SecretKey = "aliyunoss_yntkkjminioadmin";//秘密密钥
                option.IsEnableHttps = true;//是否启用Https,建议启用
                option.IsEnableCache = true;//是否启用缓存,启用后将缓存签名URL，以减少请求次数
            });
            #endregion

            #region 腾讯云COS
            services.AddOSSService("qcloud", option =>
            {
                option.Provider = OSSProvider.QCloud;//提供者,枚举值支持：Minio/Aliyun/QCloud
                option.Endpoint = "";//腾讯云COS中配置节点Endpoint表示AppId
                option.AccessKey = "qcloud_yntkkjminioadmin";//访问密钥
                option.SecretKey = "qcloud_yntkkjminioadmin";//秘密密钥
                option.IsEnableHttps = false;//是否启用Https,建议启用
                option.IsEnableCache = true;//是否启用缓存,启用后将缓存签名URL，以减少请求次数
            });
            #endregion

            #endregion

            #region 微信
            services.AddSenparcGlobalServices(App.Configuration)//Senparc.CO2NET 全局注册
                     .AddSenparcWeixinServices(App.Configuration);//Senparc.Weixin 注册（如果使用Senparc.Weixin SDK则添加）
            services.AddSession();
            services.AddMemoryCache();//使用本地缓存必须添加
            services.AddSenparcWeixinServices(App.Configuration);
            #endregion

            #region WebApi接口请求限制
            services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
            services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
            //每分钟 500 个请求和每小时 3600 个请求
            services.Configure<ClientRateLimitOptions>(options =>
            {
                options.GeneralRules = new List<RateLimitRule>
                {
                   new RateLimitRule
                   {
                       Endpoint = "*",
                       Period = "1m",
                       Limit = 500,
                    },
                   new RateLimitRule
                   {
                       Endpoint = "*",
                       Period = "1h",
                       Limit = 3600,
                   }
                };
            });
            services.AddSingleton<IRateLimitConfiguration, ElmahIoRateLimitConfiguration>();
            #endregion

            //添加事件总线服务
            services.AddEventBridge();
        }

        /// <summary>
        /// 配置请求管道
        /// 此方法由运行时调用,使用此方法配置HTTP请求管道
        /// </summary>
        /// <param name="app">提供配置应用程序请求管道</param>
        /// <param name="env">提供有关应用程序正在运行的web宿主环境的信息</param>
        /// <param name="senparcSetting">CO2NET 全局设置</param>
        /// <param name="senparcWeixinSetting">Senparc.Weixin JSON 配置</param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
            IOptions<SenparcSetting> senparcSetting, IOptions<SenparcWeixinSetting> senparcWeixinSetting)
        {
            //检查当前主机环境名称是否为Microsoft.Extensions.Hosting.EnvironmentName.Development
            if (env.IsDevelopment())
            {
                //捕获同步和异步系统,从管道中获取异常实例，并生成HTML错误响应
                app.UseDeveloperExceptionPage();
            }
            else
            {
                //向管道中添加一个中间件，该中间件将捕获异常、记录异常、重置请求路径并重新执行请求
                //如果响应已经开始，则不会重新执行请求
                app.UseExceptionHandler("/Home/Error");
                //添加使用HSTS的中间件,这将添加严格传输安全标头
                app.UseHsts();
            }

            //  NGINX 反向代理获取真实IP
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                /* 确定应该处理哪些转发器
                 * None:不处理任何转发器
                 * XForwardedHost:标识客户端请求的原始主机
                 * XForwardedFor:标识客户端的原始IP地址
                 * XForwardedProto:标识客户端用于连接的协议(HTTP或HTTPS)
                 * All:X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto
                */
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            // 添加状态码拦截中间件
            app.UseUnifyResultStatusCodes();

            //添加中间件将HTTP请求重定向到HTTPS
            app.UseHttpsRedirection(); //强制https

            //为当前请求路径启用静态文件服务
            app.UseStaticFiles();

            // Serilog请求日志中间件---必须在 UseStaticFiles 和 UseRouting 之间
            app.UseSerilogRequestLogging();

            /* 将Microsoft.AspNetCore.WebSockets.WebSocketMiddleware添加到请求管道
             * 参数：
             *     app:要配置的Microsoft.AspNetCore.Builder.IApplicationBuilder
             *     options:Microsoft.AspNetCore.Builder.WebSocketOptions
             *     将用于Microsoft.AspNetCore.WebSockets.WebSocketMiddleware
             * 返回结果:Microsoft.AspNetCore.Builder.IApplicationBuilder
             */
            app.UseWebSockets(new WebSocketOptions()
            {
                //获取或设置发送Ping/Pong保持活动控制帧的频率,默认为2分钟
                KeepAliveInterval = TimeSpan.FromSeconds(20),
                //获取或设置用于接收和解析帧的协议缓冲区的大小,默认值是4kb
                ReceiveBufferSize = 4 * 1024
            });

            //app.UseMiddleware<ArgDecryptMessageProcesssingHandler>();//请求预处理,报文解密

            //向应用程序的请求管道添加中间件类型
            app.UseMiddleware<WebSocketHandlerMiddleware>();//添加WebSocket中间件

            //添加Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware
            //到指定的Microsoft.AspNetCore.Builder.IApplicationBuilder
            app.UseRouting();
            //app.UseCors("any").UseEndpoints(endpoints =>
            //{
            //    endpoints.MapControllers();
            //});

            //添加跨域中间件
            app.UseCorsAccessor();
            // 允许所有跨域，cors是在ConfigureServices方法中配置的跨域策略名称
            //app.UseCors("any");

            //将Microsoft.AspNetCore.Authentication.AuthenticationMiddleware
            //添加到指定的Microsoft.AspNetCore.Builder中
            //IApplicationBuilder，它支持身份验证功能
            app.UseAuthentication();

            /* 将Microsoft.AspNetCore.Authorization.AuthorizationMiddleware
             * 添加到指定的Microsoft.AspNetCore.Builder
             * IApplicationBuilder，它支持授权功能
             * 当授权使用端点路由路由的资源时，
             * 这个调用必须出现在对app.UseRouting()和app.UseEndpoints(…)的调用之间，以便中间件正常工作
             */
            app.UseAuthorization();

            //注入基础中间件（带Swagger）
            app.UseInject(string.Empty);

            /* 将Microsoft.AspNetCore.Routing.EndpointMiddleware
             * 添加到指定的Microsoft.AspNetCore.Builder.IApplicationBuilder中，
             * 其中Microsoft.AspNetCore.Routing.EndpointDataSource实例
             * 从配置好的Microsoft.AspNetCore.Routing.IEndpointRouteBuilder中构建
             * */
            app.UseEndpoints(endpoints =>
            {
                //将控制器动作的端点添加到Microsoft.AspNetCore.Routing.IEndpointRouteBuilder中，
                //并指定一个具有给定名称、模式、默认值、约束和数据标记的路由
                endpoints.MapControllerRoute(
                    name: "default",//路由的名称
                    pattern: "{controller=Home}/{action=Index}/{id?}");//路由的URL模式
            });

            // 设置雪花id的workerId，确保每个实例workerId都应不同
            var workerId = ushort.Parse(App.Configuration["SnowId:WorkerId"] ?? "1");
            YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = workerId, WorkerIdBitLength = 16 });

            #region 微信
            //启动 CO2NET 全局注册，必须！
            IRegisterService register = RegisterService.Start(senparcSetting.Value).UseSenparcGlobal();
            //微信全局注册,必须！
            register.UseSenparcWeixin(senparcWeixinSetting.Value, senparcSetting.Value);
            #endregion

            // 开启自启动定时任务
            App.GetService<ITimeTaskService>().StartTimerJob();

           
        }

        /// <summary>
        /// 配置SqlSugar
        /// </summary>
        /// <param name="services"></param>
        private void SqlSugarConfigure(IServiceCollection services)
        {
            #region 配置sqlsuagr

            List<ConnectionConfig> connectConfigList = new List<ConnectionConfig>();
            var connectionStr = $"{App.Configuration["ConnectionStrings:DefaultConnection"]}";
            var dataBase = $"{App.Configuration["ConnectionStrings:DBName"]}";
            var dbType = (DbType)Enum.Parse(typeof(DbType), App.Configuration["ConnectionStrings:DBType"]);
            var ConfigId = $"{App.Configuration["ConnectionStrings:ConfigId"]}";
            var iocDbType = (IocDbType)Enum.Parse(typeof(IocDbType), App.Configuration["ConnectionStrings:DBType"]);

            SugarIocServices.AddSqlSugar(new IocConfig()
            {
                ConnectionString = string.Format(connectionStr, dataBase),
                DbType = iocDbType,
                ConfigId= ConfigId,
                IsAutoCloseConnection = true//自动释放
            });

            connectConfigList.Add(new ConnectionConfig
            {
                ConnectionString = string.Format(connectionStr, dataBase),
                DbType = dbType,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute,
                ConfigId = ConfigId,
                ConfigureExternalServices = new ConfigureExternalServicesExtenisons()
                {
                    EntityNameServiceType = typeof(SugarTable)//这个不管是不是自定义都要写，主要是用来获取所有实体
                }
            });

            services.AddSqlSugar(connectConfigList.ToArray(), db =>
            {
                db.Aop.OnLogExecuting = (sql, pars) =>
                {
                    if (sql.StartsWith("SELECT"))
                    {
                        Console.ForegroundColor = ConsoleColor.Green;
                    }
                    if (sql.StartsWith("UPDATE") || sql.StartsWith("INSERT"))
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                    if (sql.StartsWith("DELETE"))
                    {
                        Console.ForegroundColor = ConsoleColor.Blue;
                    }
                    //在控制台输出sql语句
                    Console.WriteLine(SqlProfiler.ParameterFormat(sql, pars));
                    //App.PrintToMiniProfiler("SqlSugar", "Info", SqlProfiler.ParameterFormat(sql, pars));
                };
            });

            #endregion
        }
   
        /// <summary>
        /// 配置MongoDB
        /// </summary>
        /// <param name="services"></param>      
        private void MongoDbConfigure(IServiceCollection services)
        {
            var connectionStr = $"{App.Configuration["MongoDBConnectionSettings:MongoDBConnection"]}";
            var dataBase = $"{App.Configuration["MongoDBConnectionSettings:MongoDBName"]}";
            //注册mongodb上下文
            services.AddMongoDBContext<YNTKDbMongoDBContext>(options => {
                options.Configuration = connectionStr;
                options.DatabaseName = dataBase;
            });
            // 注册MongoDB仓储
            services.AddScoped(typeof(IMongoDBRepository<>), typeof(MongoDBRepository<>));
        }

    }
}
